/*
 * (c) Copyright Ascensio System SIA 2010-2024
 *
 * This program is a free software product. You can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License (AGPL)
 * version 3 as published by the Free Software Foundation. In accordance with
 * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect
 * that Ascensio System SIA expressly excludes the warranty of non-infringement
 * of any third-party rights.
 *
 * This program is distributed WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR  PURPOSE. For
 * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
 *
 * You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish
 * street, Riga, Latvia, EU, LV-1050.
 *
 * The  interactive user interfaces in modified source and object code versions
 * of the Program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU AGPL version 3.
 *
 * Pursuant to Section 7(b) of the License you must retain the original Product
 * logo when distributing the program. Pursuant to Section 7(e) we decline to
 * grant you any rights under trademark law for use of our trademarks.
 *
 * All the Product's GUI elements, including illustrations and icon sets, as
 * well as technical writing content are licensed under the terms of the
 * Creative Commons Attribution-ShareAlike 4.0 International. See the License
 * terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
 *
 */

"use strict";

(function(window, undefined) {

	function CFontManagerEngine()
	{
		this.library = AscFonts.FT_CreateLibrary();
		this.manager = null;

		this.openFont = function(stream, faceindex)
		{
			var face = AscFonts.FT_Open_Face(this.library, stream, faceindex);
			if (!face)
				return null;

			var font = new AscFonts.CFontFile();
			font.SetFace(face, this.manager);

			if (!font.IsSuccess())
			{
				font = null;
				face = null;
				return null;
			}

			return font;
		};

		this.setHintsProps = function(bIsHinting, bIsSubpixHinting)
		{
			var REND_MODE_SUBPIX = (bIsHinting && bIsSubpixHinting) ? AscFonts.TT_INTERPRETER_VERSION_40 : AscFonts.TT_INTERPRETER_VERSION_35;
			this.manager.LOAD_MODE = bIsHinting ? AscFonts.LOAD_MODE_HINTING : AscFonts.LOAD_MODE_DEFAULT;

			AscFonts.FT_Set_TrueType_HintProp(this.library, REND_MODE_SUBPIX);
		};

		this.destroy = function()
		{
			AscFonts.FT_Done_Library(this.library);
		};
	}

	function CFontFilesCache()
	{
		this.m_lMaxSize = 1000;
		this.m_lCurrentSize = 0;
		this.Fonts = {};

		this.LoadFontFile = function(stream_index, name, faceindex, fontManager)
		{
			if (!fontManager._engine)
			{
				fontManager._engine = new CFontManagerEngine();
				fontManager._engine.manager = fontManager;
				fontManager._engine.setHintsProps(fontManager.bIsHinting, fontManager.bIsSubpixHinting);
			}

			// correct native stream
			if (AscFonts.CreateNativeStreamByIndex)
				AscFonts.CreateNativeStreamByIndex(stream_index);

			if (!AscFonts.g_fonts_streams[stream_index])
				return null;

			return fontManager._engine.openFont(AscFonts.g_fonts_streams[stream_index], faceindex);
		};

		this.LockFont = function(stream_index, fontName, faceIndex, fontSize, _ext, fontManager)
		{
			var key = fontName + faceIndex + fontSize;
			if (undefined !== _ext)
				key += _ext;
			var pFontFile = this.Fonts[key];
			if (pFontFile)
				return pFontFile;

			pFontFile = this.Fonts[key] = this.LoadFontFile(stream_index, fontName, faceIndex, fontManager);
			return pFontFile;
		};
	}

	AscFonts.TextMeasureMode = {
		Word : 0,
		Cell : 1,
		Slide : 2
	};

	// params: { mode: "cell" };
	function CFontManager(params)
	{
		this._engine = null;

		this.m_pFont = null;
		this.m_oGlyphString = new AscFonts.CGlyphString();

		this.error = 0;

		this.fontName = undefined;

		this.m_fCharSpacing = 0.0;
		this.m_bStringGID = false;

		this.m_oFontsCache = null;

		this.m_lUnits_Per_Em = 0;
		this.m_lAscender = 0;
		this.m_lDescender = 0;
		this.m_lLineHeight = 0;

		this.RasterMemory = null;

		this.Mode = AscFonts.TextMeasureMode.Word;
		this.IsAdvanceNeedBoldFonts = false;

		this.IsUseWinOS2Params = true;

		this.bIsHinting = false;
		this.bIsSubpixHinting = false;

		this.LOAD_MODE = 40970;

		this.SetParams(params);
	}

	CFontManager.prototype =
		{
			SetParams : function(params)
			{
				if (params)
				{
					switch (params.mode)
					{
						case "cell":
							this.Mode = AscFonts.TextMeasureMode.Cell;
							break;
						case "slide":
							this.Mode = AscFonts.TextMeasureMode.Slide;
							break;
						default:
							break;
					}
				}

				this.IsAdvanceNeedBoldFonts = (AscFonts.TextMeasureMode.Cell === this.Mode) ? true : false;
			},

			AfterLoad : function()
			{
				if (null == this.m_pFont)
				{
					this.m_lUnits_Per_Em = 0;
					this.m_lAscender = 0;
					this.m_lDescender = 0;
					this.m_lLineHeight = 0;
				}
				else
				{
					var f = this.m_pFont;
					this.m_lUnits_Per_Em = f.m_lUnits_Per_Em;
					this.m_lAscender = f.m_lAscender;
					this.m_lDescender = f.m_lDescender;
					this.m_lLineHeight = f.m_lLineHeight;

					f.CheckHintsSupport();
				}
			},

			Initialize : function(is_init_raster_memory)
			{
				this.m_oFontsCache = new CFontFilesCache();

				if (is_init_raster_memory === true)
				{
					AscFonts.registeredFontManagers.push(this);
					this.InitializeRasterMemory();
				}
			},

			InitializeRasterMemory : function()
			{
				if (AscFonts.use_map_blitting)
				{
					if (!this.RasterMemory)
					{
						this.RasterMemory = new AscFonts.CRasterHeapTotal();
						this.RasterMemory.CreateFirstChuck();
					}
				}
				else
				{
					if (this.RasterMemory)
					{
						this.RasterMemory = null;
					}
				}
			},

			ClearFontsRasterCache : function()
			{
				for (var i in this.m_oFontsCache.Fonts)
				{
					if (this.m_oFontsCache.Fonts[i])
						this.m_oFontsCache.Fonts[i].ClearCache();
				}
				this.ClearRasterMemory();
			},

			ClearRasterMemory : function()
			{
				// быстрая очистка всей памяти (убирание всех дыр)
				if (null == this.RasterMemory || null == this.m_oFontsCache)
					return;

				var _fonts = this.m_oFontsCache.Fonts;
				for (var i in _fonts)
				{
					if (_fonts[i] !== undefined && _fonts[i] != null)
						_fonts[i].ClearCacheNoAttack();
				}

				this.RasterMemory.Clear();
			},

			UpdateSize : function(dOldSize, dDpi, dNewDpi)
			{
				if (0 == dNewDpi)
					dNewDpi = 72.0;
				if (0 == dDpi)
					dDpi = 72.0;

				return dOldSize * dDpi / dNewDpi;
			},

			LoadString : function(wsBuffer, fX, fY)
			{
				if (!this.m_pFont)
					return false;

				this.m_oGlyphString.SetString(wsBuffer, fX, fY);
				this.m_pFont.GetString(this.m_oGlyphString);

				return true;
			},

			LoadString2 : function(wsBuffer, fX, fY)
			{
				if (!this.m_pFont)
					return false;

				this.m_oGlyphString.SetString(wsBuffer, fX, fY);
				this.m_pFont.GetString2(this.m_oGlyphString);

				return true;
			},

			LoadString3 : function(gid, fX, fY)
			{
				if (!this.m_pFont)
					return false;

				this.SetStringGID(true);
				this.m_oGlyphString.SetStringGID (gid, fX, fY);
				this.m_pFont.GetString2(this.m_oGlyphString);
				this.SetStringGID(false);

				return true;
			},

			LoadString3C : function(gid, fX, fY, codepoints)
			{
				if (!this.m_pFont)
					return false;

				this.SetStringGID(true);

				// это SetString
				var string = this.m_oGlyphString;
				this.m_pFont.codePointsForGid = codepoints;

				string.m_fX = fX + string.m_fTransX;
				string.m_fY = fY + string.m_fTransY;

				string.m_nGlyphsCount = 1;
				string.m_nGlyphIndex  = 0;

				var _g = string.GetFirstGlyph();
				_g.bBitmap = false;
				_g.oBitmap = null;
				_g.eState = AscFonts.EGlyphState.glyphstateNormal;
				_g.lUnicode = gid;

				this.m_pFont.GetString2C(string);

				this.SetStringGID(false);

				this.m_pFont.codePointsForGid = undefined;
				return true;
			},

			LoadString2C : function(wsBuffer, fX, fY)
			{
				if (!this.m_pFont)
					return false;

				// это SetString
				var string = this.m_oGlyphString;

				string.m_fX = fX + string.m_fTransX;
				string.m_fY = fY + string.m_fTransY;

				string.m_nGlyphsCount = 1;
				string.m_nGlyphIndex  = 0;

				var _g = string.GetFirstGlyph();
				_g.bBitmap = false;
				_g.oBitmap = null;
				_g.eState = AscFonts.EGlyphState.glyphstateNormal;
				_g.lUnicode = wsBuffer.charCodeAt(0);

				this.m_pFont.GetString2C(string);
				return string.m_fEndX;
			},

			LoadString4C : function(lUnicode, fX, fY)
			{
				if (!this.m_pFont)
					return false;

				// это SetString
				var string = this.m_oGlyphString;

				string.m_fX = fX + string.m_fTransX;
				string.m_fY = fY + string.m_fTransY;

				string.m_nGlyphsCount = 1;
				string.m_nGlyphIndex  = 0;

				var _g = string.GetFirstGlyph();
				_g.bBitmap = false;
				_g.oBitmap = null;
				_g.eState = AscFonts.EGlyphState.glyphstateNormal;
				_g.lUnicode = lUnicode;

				this.m_pFont.GetString2C(string);
				return string.m_fEndX;
			},

			LoadStringPathCode : function(code, isGid, fX, fY, worker)
			{
				if (!this.m_pFont)
					return false;

				this.SetStringGID(isGid);

				// это SetString
				var string = this.m_oGlyphString;

				string.m_fX = fX + string.m_fTransX;
				string.m_fY = fY + string.m_fTransY;

				string.m_nGlyphsCount = 1;
				string.m_nGlyphIndex  = 0;

				var _g = string.GetFirstGlyph();
				_g.bBitmap = false;
				_g.oBitmap = null;
				_g.eState = AscFonts.EGlyphState.glyphstateNormal;
				_g.lUnicode = code;

				this.m_pFont.GetStringPath(string, worker);

				this.SetStringGID(false);

				return true;
			},

			LoadChar : function(lUnicode)
			{
				if (!this.m_pFont)
					return false;

				return this.m_pFont.GetChar2(lUnicode);
			},

			MeasureChar : function(lUnicode, is_raster_distances)
			{
				if (!this.m_pFont)
					return;

				return this.m_pFont.GetChar(lUnicode, is_raster_distances);
			},

			GetKerning : function(unPrevGID, unGID)
			{
				if (!this.m_pFont)
					return;

				return this.m_pFont.GetKerning(unPrevGID, unGID);
			},

			MeasureString : function()
			{
				var oPoint = new AscFonts.CGlyphRect();
				var len = this.m_oGlyphString.GetLength();
				if (len <= 0)
					return oPoint;

				var fTop = 0xFFFF, fBottom = -0xFFFF, fLeft = 0xFFFF, fRight = -0xFFFF;
				for (var nIndex = 0; nIndex < len; ++nIndex)
				{
					var oSizeTmp = this.m_oGlyphString.GetBBox (nIndex);

					if (fBottom < oSizeTmp.fBottom)
						fBottom = oSizeTmp.fBottom;

					if (fTop > oSizeTmp.fTop)
						fTop = oSizeTmp.fTop;

					if (fLeft > oSizeTmp.fLeft)
						fLeft = oSizeTmp.fLeft;

					if (fRight < oSizeTmp.fRight)
						fRight = oSizeTmp.fRight;
				}

				oPoint.fX = fLeft;
				oPoint.fY = fTop;

				oPoint.fWidth  = Math.abs((fRight - fLeft));
				oPoint.fHeight = Math.abs((fTop - fBottom));

				return oPoint;
			},

			MeasureString2 : function()
			{
				var oPoint = new AscFonts.CGlyphRect();

				if (this.m_oGlyphString.GetLength() <= 0)
					return oPoint;

				var oSizeTmp = this.m_oGlyphString.GetBBox2();

				oPoint.fX = oSizeTmp.fLeft;
				oPoint.fY = oSizeTmp.fTop;

				oPoint.fWidth  = Math.abs((oSizeTmp.fRight - oSizeTmp.fLeft));
				oPoint.fHeight = Math.abs((oSizeTmp.fTop - oSizeTmp.fBottom));

				return oPoint;
			},

			GetNextChar2 : function()
			{
				return this.m_oGlyphString.GetNext();
			},

			IsSuccess : function()
			{
				return (0 == this.error);
			},

			SetTextMatrix : function(fA, fB, fC, fD, fE, fF)
			{
				if (!this.m_pFont)
					return false;

				if (this.m_pFont.SetTextMatrix (fA, fB, fC, fD, 0, 0))
					this.m_oGlyphString.SetCTM(fA, fB, fC, fD, 0, 0);
				this.m_oGlyphString.SetTrans(fE, fF);

				return true;
			},

			SetTextMatrix2 : function(fA, fB, fC, fD, fE, fF)
			{
				if (!this.m_pFont)
					return false;

				this.m_pFont.SetTextMatrix (fA, fB, fC, fD, 0, 0);
				this.m_oGlyphString.SetCTM(fA, fB, fC, fD, 0, 0);
				this.m_oGlyphString.SetTrans(fE, fF);

				return true;
			},

			SetStringGID : function(bStringGID)
			{
				this.m_bStringGID = bStringGID;

				if (!this.m_pFont)
					return;

				this.m_pFont.SetStringGID(this.m_bStringGID);
			},

			SetHintsProps : function(bIsHinting, bIsSubpixHinting)
			{
				this.bIsHinting = bIsHinting;
				this.bIsSubpixHinting = bIsSubpixHinting;

				if (this._engine)
					this._engine.setHintsProps(bIsHinting, bIsSubpixHinting);

				this.ClearFontsRasterCache();
			},

			SetAdvanceNeedBoldFonts : function(value)
			{
				this.IsAdvanceNeedBoldFonts = value;
			},

			LoadFont : function(fontFile, faceIndex, size, isBold, isItalic, needBold, needItalic, isNoSetupToManager)
			{
				var _ext = "";
				if (needBold)
					_ext += "nbold";
				if (needItalic)
					_ext += "nitalic";

				var pFontFile = this.m_oFontsCache.LockFont(fontFile.stream_index, fontFile.Id, faceIndex, size, _ext, this);
				if (!pFontFile)
					return null;

				pFontFile.m_oFontManager = this;

				pFontFile.SetNeedBold(needBold);
				pFontFile.SetNeedItalic(needItalic);

				pFontFile.SetStringGID(this.m_bStringGID);
				pFontFile.SetCharSpacing(this.m_fCharSpacing);

				if (isNoSetupToManager !== true)
				{
					this.m_pFont = pFontFile;
					this.m_oGlyphString.ResetCTM();
					this.AfterLoad();
				}

				return pFontFile;
			},

			GetLimitsY : function()
			{
				if (this.m_pFont)
					return this.m_pFont.GetLimitsY();
				return {
					min : 0,
					max : 0
				};
			}
		}

	window['AscFonts'].CFontManager = CFontManager;
	window['AscFonts'].CFontFilesCache = CFontFilesCache;
})(window);
